package mips;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import llvmir.type.Type;
import llvmir.value.Value;
import llvmir.value.constants.ConstantChar;
import llvmir.value.constants.ConstantInt;
import llvmir.value.instructions.*;
import llvmir.value.structure.Module;
import mips.instructions.Branch;
import mips.instructions.MipsBinaryOp;
import mips.instructions.Move;
import mips.instructions.Sw;
import mips.instructions.J;
import mips.instructions.Jal;
import mips.instructions.Jr;
import mips.instructions.Loading;
import mips.structure.MipsBlock;
import mips.structure.MipsFunc;
import mips.structure.MipsGV;
import mips.structure.MipsModule;
import mips.value.Data;
import mips.value.Immediate;
import mips.value.MipsValue;
import mips.value.Register;
import mips.value.VirRegister;
import optimizer.OtherOptimizer;
import llvmir.value.structure.BasicBlock;
import llvmir.value.structure.Function;
import llvmir.value.structure.GlobalVariable;
import llvmir.value.structure.Instruction;

public class MipsBuilder {
    private static MipsBuilder instance;
    private BufferedWriter writer;
    private MipsModule mipsModule = MipsModule.getInstance();
    private Module module = Module.getInstance();
    private boolean isMain = false;
    private int stackOffset = 0;
    private MipsFunc currFunc;
    private MipsBlock currBlock;
    private boolean jumpInstr = false;

    private MipsBuilder() {
    }

    public static MipsBuilder getInstance() {
        if (instance == null) {
            instance = new MipsBuilder();
        }
        return instance;
    }

    public void buildMipsModule() {
        // 这个函数只是用来处理从LLVM IR到MIPS的前置过程
        // 对于全局变量，转存即可
        for (Value gv : module.getGlobalVariables()) {
            mipsModule.globalVariables.add(
                    new MipsGV(gv.getName().substring(1),
                            ((GlobalVariable) gv).getFirstOperand()));
            // 为每个全局的指针分配寄存器，实际上就是一个Data类型
            gv.setRegister(new Data(gv.getName().substring(1)));
        }
        // 对于函数，需要先计算每个函数的栈大小
        for (Value function : module.getFunctions()) {
            calculateStackSize((Function) function);
        }
        calculateStackSize(module.getMainFunction());
        // 对于每个函数，需要将其转换为MIPS的函数
        for (Value function : module.getFunctions()) {
            buildMipsFunction((Function) function);
        }
        isMain = true;
        buildMipsFunction(module.getMainFunction());
    }

    private void calculateStackSize(Function function) {
        int size = 4 + 18 * 4;
        for (BasicBlock basicBlock : function.getBasicBlocks()) {
            for (Value instruction : basicBlock.getInstructions()) {
                if (instruction instanceof Alloca) {
                    size += buildAllocas((Alloca) instruction);
                }
            }
        }
        String funcName = function.getName().substring(1);
        MipsFunc mipsFunc = new MipsFunc(funcName);
        mipsFunc.setStackSize(size);
        if (funcName.equals("main"))
            mipsModule.main = mipsFunc;
        else
            mipsModule.functions.add(mipsFunc);
    }

    private void buildMipsFunction(Function function) {
        // 1. 计算函数的栈大小，已完成
        String funcName = function.getName().substring(1);
        if (funcName.equals("main")) {
            currFunc = mipsModule.main;
        } else
            currFunc = mipsModule.getFunction(function.getName().substring(1));
        // 2. 为参数分配寄存器
        int argumentRegNum = Register.getArgumentegistersCount();
        for (int i = 0; i < function.getParams().size(); i++) {
            if (i < argumentRegNum) {
                function.getParams().get(i).setRegister(Register.getArgumenRegister());
            } else {
                // 如果参数的数量超过了寄存器的数量，那么就需要将参数存入栈中
                function.getParams().get(i).setRegister(new VirRegister(4 * i));
            }
        }
        // 对每个函数的基本块进行处理
        for (BasicBlock basicBlock : function.getBasicBlocks()) {
            currFunc.blocks.add(buildMipsBasicBlock(basicBlock, function.getBasicBlocks().indexOf(basicBlock),
                    function.getParams().size()));
        }
        Register.releaseArgumentRegisters();
        Register.releaseTempRegisters(currFunc);
        stackOffset = 0;
        currFunc = null;
    }

    private MipsBlock buildMipsBasicBlock(BasicBlock bb, int index, int paramNum) {
        MipsBlock mipsBlock = new MipsBlock(bb.getParent().getName().substring(1) + "_" + bb.getName());
        for (int i = 0; i < bb.getInstructions().size(); i++) {
            Value instruction = bb.getInstructions().get(i);
            currBlock = mipsBlock;
            jumpInstr = false;
            buildMipsInstruction(instruction);
            // 从后面的指令的operand中找这个指令及其相关指令，如果没有，那么就释放寄存器
            tryToReleaseRegister(instruction, bb, i);
            if (jumpInstr)
                break;
        }
        Register.releaseTempRegisters(currFunc);
        currBlock = null;
        return mipsBlock;
    }

    public void tryToReleaseRegister(Value instruction, BasicBlock bb, int i) {
        Instruction nowInstruction = (Instruction) instruction;
        int flag = 0;
        for (int j = i + 1; j < bb.getInstructions().size(); j++) {
            Instruction nextInstruction = (Instruction) bb.getInstructions().get(j);
            if (nextInstruction.getOperands() == null || (nextInstruction.getOperands().size() == 0)) {
                continue;
            }
            for (Value operand : nextInstruction.getOperands()) {
                if (operand == nowInstruction) {
                    flag++;
                }
            }
        }

        if (flag == 0 && instruction.getRegister() != null) {
            instruction.clearRegister(currFunc);
        }
        if (((Instruction) instruction).getOperands() == null
                || ((Instruction) instruction).getOperands().size() == 0) {
            return;
        }
        for (Value operand : nowInstruction.getOperands()) {
            int flags = 0;
            for (int j = i + 1; j < bb.getInstructions().size(); j++) {
                Instruction nextInstruction = (Instruction) bb.getInstructions().get(j);
                if (nextInstruction.getOperands() == null || (nextInstruction.getOperands().size() == 0)) {
                    continue;
                }
                for (Value operandNext : nextInstruction.getOperands()) {
                    if (operand == operandNext) {
                        flags++;
                    }
                }
            }
            if (flags == 0 && operand != null && operand.getRegister() != null) {
                operand.clearRegister(currFunc);
            }
        }
    }

    private void buildMipsInstruction(Value instruction) {
        if (instruction instanceof Alloca) {
            Alloca alloca = (Alloca) instruction;
            // 为alloca分配寄存器
            if (alloca.getRegister() == null) {
                // 如果是参数，那么就不需要分配寄存器
                alloca.setRegister(new VirRegister(stackOffset));
                stackOffset += alloca.getAllocType().getSize();
            }
        } else if (instruction instanceof BinaryOp) {
            BinaryOp binaryOp = (BinaryOp) instruction;
            Value lhs = binaryOp.getOperands().get(0);
            Value rhs = binaryOp.getOperands().get(1);
            // 如果指令的value没有寄存器，则为其分配一个寄存器
            if (instruction.getRegister() == null) {
                instruction.setRegister(Register.getTempRegister(currFunc));
            }
            if (lhs.getRegister() == null) {
                if (lhs instanceof ConstantInt) {
                    lhs.setRegister(new Immediate(((ConstantInt) lhs).getValue()));
                } else if (lhs instanceof ConstantChar) {
                    lhs.setRegister(new Immediate(((ConstantChar) lhs).getValue()));
                } else {
                    lhs.setRegister(Register.getTempRegister(currFunc));
                }
            }
            if (rhs.getRegister() == null) {
                if (rhs instanceof ConstantInt) {
                    rhs.setRegister(new Immediate(((ConstantInt) rhs).getValue()));
                } else if (rhs instanceof ConstantChar) {
                    rhs.setRegister(new Immediate(((ConstantChar) rhs).getValue()));
                } else {
                    rhs.setRegister(Register.getTempRegister(currFunc));
                }
            }
            MipsBinaryOp mbo = new MipsBinaryOp(binaryOp.getOp(),
                    instruction.getRegister(), lhs.getRegister(), rhs.getRegister());
            currBlock.addInstruction(mbo);
        } else if (instruction instanceof Br) {
            Br br = (Br) instruction;
            if (br.getOperands().size() == 1) {
                currBlock.addInstruction(new J(currFunc.getName() + "_" + br.getOperands().get(0).getName()));
            } else {
                // 如果指令的value没有寄存器，则为其分配一个寄存器
                if (br.getOperands().get(2).getRegister() == null) {
                    br.getOperands().get(2).setRegister(Register.getTempRegister(currFunc));
                }
                currBlock.addInstruction(
                        new Branch("==", br.getOperands().get(2).getRegister(),
                                Register.ZERO, currFunc.getName() + "_" + br.getOperands().get(1).getName(),
                                currFunc.getName() + "_" + br.getOperands().get(0).getName()));
            }
        } else if (instruction instanceof Call) {
            // 有参调用需要将参数放入寄存器，那么这里就需要将寄存器存到类中
            Call call = (Call) instruction;
            if (!call.getFirstOperand().getType().isVoidType()) {
                if (call.getRegister() == null) {
                    call.setRegister(Register.getTempRegister(currFunc));
                }
            }
            if (call.getOperands().size() == 1) {
                // 无参数的函数调用
                String funcName = call.getOperands().get(0).getName().substring(1);
                int offset = 0;
                if (funcName.equals("putint") || funcName.equals("putch") || funcName.equals("getint")
                        || funcName.equals("getchar") || funcName.equals("putstr")) {
                    offset = -1;
                } else {
                    offset = mipsModule.getFunction(funcName).getStackSize();
                }

                currBlock.addInstruction(new Jal(funcName, offset, currFunc));
            } else {
                List<MipsValue> args = new ArrayList<>();
                // 这里只需要将这一堆参数放入list中即可
                for (int i = 1; i < call.getOperands().size(); i++) {
                    Value arg = call.getOperands().get(i);
                    if (arg.getRegister() == null) {
                        if (arg instanceof ConstantInt) {
                            arg.setRegister(new Immediate(((ConstantInt) arg).getValue()));
                        } else if (arg instanceof ConstantChar) {
                            arg.setRegister(new Immediate(((ConstantChar) arg).getValue()));
                        } else {
                            arg.setRegister(Register.getTempRegister(currFunc));
                        }
                    }
                    args.add(call.getOperands().get(i).getRegister());
                }
                String funcName = call.getOperands().get(0).getName().substring(1);
                // 库函数不需要传栈的大小
                int offset = 0;
                if (funcName.equals("putint") || funcName.equals("putch") || funcName.equals("getint")
                        || funcName.equals("getchar") || funcName.equals("putstr")) {
                    offset = -1;
                } else {
                    offset = mipsModule.getFunction(funcName).getStackSize();
                }

                currBlock.addInstruction(new Jal(funcName, args, offset, currFunc));
            }
            // 如果有返回值，那么就需要将返回值存入寄存器
            if (!call.getFirstOperand().getType().isVoidType()) {
                currBlock.addInstruction(new Move(call.getRegister(), Register.V0));
            }
        } else if (instruction instanceof GetElementPtr) {
            // 为数组和偏移量分配寄存器
            GetElementPtr gep = (GetElementPtr) instruction;
            if (gep.getRegister() == null) {
                gep.setRegister(Register.getTempRegister(currFunc));
            }
            if (gep.getOperands().get(0).getRegister() == null) {
                gep.getOperands().get(0).setRegister(Register.getTempRegister(currFunc));
            }
            if (gep.getOperands().get(1).getRegister() == null) {
                if (gep.getOperands().get(1) instanceof ConstantInt) {
                    gep.getOperands().get(1)
                            .setRegister(new Immediate(((ConstantInt) gep.getOperands().get(1)).getValue() * 4));
                } else if (gep.getOperands().get(1) instanceof ConstantChar) {
                    gep.getOperands().get(1)
                            .setRegister(new Immediate(((ConstantChar) gep.getOperands().get(1)).getValue() * 4));
                } else {
                    gep.getOperands().get(1).setRegister(Register.getTempRegister(currFunc));
                }
            }
            currBlock.addInstruction(new Loading(gep.getRegister(), gep.getOperands().get(0).getRegister(), "la"));
            MipsValue temp = gep.getOperands().get(1).getRegister();
            if (temp instanceof Register) {
                // 如果是寄存器，那么就需要将其值左移两位
                Register li = Register.getTempRegister(currFunc);
                currBlock.addInstruction(
                        new MipsBinaryOp("<<", li, temp, new Immediate(2)));
                temp = li;
                Register.releaseTempRegister((Register) temp, currFunc);
            }
            if (!((temp instanceof Immediate) && ((Immediate) temp).getValue() == 0) && OtherOptimizer.OPEN) {
                currBlock.addInstruction(new MipsBinaryOp("+", gep.getRegister(), gep.getRegister(), temp));
            }
        } else if (instruction instanceof Icmp) {
            Icmp icmp = (Icmp) instruction;
            String op = icmp.getOp();
            Value lhs = icmp.getOperands().get(0);
            Value rhs = icmp.getOperands().get(1);
            if (instruction.getRegister() == null) {
                instruction.setRegister(Register.getTempRegister(currFunc));
            }
            if (lhs.getRegister() == null) {
                if (lhs instanceof ConstantInt) {
                    lhs.setRegister(new Immediate(((ConstantInt) lhs).getValue()));
                } else if (lhs instanceof ConstantChar) {
                    lhs.setRegister(new Immediate(((ConstantChar) lhs).getValue()));
                } else {
                    lhs.setRegister(Register.getTempRegister(currFunc));
                }
            }
            if (rhs.getRegister() == null) {
                if (rhs instanceof ConstantInt) {
                    rhs.setRegister(new Immediate(((ConstantInt) rhs).getValue()));
                } else if (rhs instanceof ConstantChar) {
                    rhs.setRegister(new Immediate(((ConstantChar) rhs).getValue()));
                } else {
                    rhs.setRegister(Register.getTempRegister(currFunc));
                }
            }
            if (OtherOptimizer.OPEN) {
                // 如果Icmp的下一条是Br，那么就将其合成为一个Branch
                // 注意的是，这里可能会和公共表达式优化冲突
                BasicBlock bb = ((Instruction) instruction).getBasicBlock();
                int index = bb.getInstructions().indexOf(instruction);
                if (index + 1 < bb.getInstructions().size()) {
                    Value nextInstruction = bb.getInstructions().get(index + 1);
                    if (nextInstruction instanceof Br) {
                        Br br = (Br) nextInstruction;
                        if (br.getOperands().size() == 3) {
                            // 说明是有条件跳转
                            // 如果指令的value没有寄存器，则为其分配一个寄存器
                            if (br.getOperands().get(2).getRegister() == null) {
                                br.getOperands().get(2).setRegister(Register.getTempRegister(currFunc));
                            }
                            currBlock.addInstruction(
                                    new Branch(op, lhs.getRegister(), rhs.getRegister(),
                                            currFunc.getName() + "_" + br.getOperands().get(0).getName(),
                                            currFunc.getName() + "_" + br.getOperands().get(1).getName()));
                            jumpInstr = true;
                            return;
                        }
                    }
                }
            }
            currBlock.addInstruction(
                    new MipsBinaryOp(op, instruction.getRegister(), lhs.getRegister(), rhs.getRegister()));
        } else if (instruction instanceof Load) {
            Load load = (Load) instruction;
            if (load.getRegister() == null) {
                load.setRegister(Register.getTempRegister(currFunc));
            }
            if (load.getOperands().get(0).getRegister() == null) {
                load.getOperands().get(0).setRegister(Register.getTempRegister(currFunc));
            }
            currBlock.addInstruction(new Loading(load.getRegister(), load.getOperands().get(0).getRegister()));
        } else if (instruction instanceof Ret) {
            // 没有返回值的ret
            Ret ret = (Ret) instruction;
            if (ret.getOperands().size() == 0 || ret.getOperands().get(0) == null) {
                currBlock.addInstruction(new Jr(currFunc.getStackSize()));
            } else {
                // 为返回值分配寄存器
                if (ret.getOperands().get(0).getRegister() == null) {
                    if (ret.getOperands().get(0) instanceof ConstantInt) {
                        ret.getOperands().get(0)
                                .setRegister(new Immediate(((ConstantInt) ret.getOperands().get(0)).getValue()));
                    } else if (ret.getOperands().get(0) instanceof ConstantChar) {
                        ret.getOperands().get(0)
                                .setRegister(new Immediate(((ConstantChar) ret.getOperands().get(0)).getValue()));
                    } else {
                        ret.getOperands().get(0).setRegister(Register.getTempRegister(currFunc));
                    }
                }
                currBlock.addInstruction(
                        new Jr(ret.getOperands().get(0).getRegister(), isMain, currFunc.getStackSize()));
            }
        } else if (instruction instanceof Store) {
            Store store = (Store) instruction;
            Value data = store.getOperands().get(0);
            if (store.getOperands().get(1).getRegister() == null) {
                store.getOperands().get(1).setRegister(Register.getTempRegister(currFunc));
            }
            if (data.getRegister() == null) {
                // 如果是常量，那么就直接赋值
                if (data instanceof ConstantInt) {
                    data.setRegister(new Immediate(((ConstantInt) data).getValue()));
                } else if (data instanceof ConstantChar) {
                    data.setRegister(new Immediate(((ConstantChar) data).getValue()));
                } else {
                    data.setRegister(Register.getTempRegister(currFunc));
                }
            }
            MipsValue temp = data.getRegister();
            if (data.getRegister() instanceof Immediate) {
                Register li = Register.getTempRegister(currFunc);
                temp = li;
                currBlock.addInstruction(new Loading(li, data.getRegister(), "li"));
                Register.releaseTempRegister(li, currFunc);
            }
            currBlock.addInstruction(
                    new Sw(temp, store.getOperands().get(1).getRegister()));
            // data.clearRegister(currFunc);
        } else if (instruction instanceof Zext) {
            // 把转换前的value的寄存器赋给转换后的value
            Zext zext = (Zext) instruction;
            if (zext.getFirstOperand() instanceof ConstantInt) {
                zext.getFirstOperand().setRegister(new Immediate(((ConstantInt) zext.getFirstOperand()).getValue()));
            } else if (zext.getFirstOperand() instanceof ConstantChar) {
                zext.getFirstOperand().setRegister(new Immediate(((ConstantChar) zext.getFirstOperand()).getValue()));
            } else {
                zext.getFirstOperand().setRegister(zext.getFirstOperand().getRegister());
            }
            zext.setRegister(Register.getTempRegister(currFunc));
            if (zext.getFirstOperand().getRegister() instanceof Immediate) {
                Register li = Register.getTempRegister(currFunc);
                currBlock.addInstruction(new Loading(li, zext.getFirstOperand().getRegister(), "li"));
                currBlock.addInstruction(new Move(zext.getRegister(), li));
                Register.releaseTempRegister(li, currFunc);
            } else {
                // zext.setRegister(zext.getFirstOperand().getRegister());
                currBlock.addInstruction(new Move(zext.getRegister(), zext.getFirstOperand().getRegister()));

            }
        } else if (instruction instanceof Trunc) {
            // 把转换前的value的寄存器赋给转换后的value
            Trunc trunc = (Trunc) instruction;
            if (trunc.getFirstOperand() instanceof ConstantInt) {
                trunc.getFirstOperand().setRegister(new Immediate(((ConstantInt) trunc.getFirstOperand()).getValue()));
            } else if (trunc.getFirstOperand() instanceof ConstantChar) {
                trunc.getFirstOperand().setRegister(new Immediate(((ConstantChar) trunc.getFirstOperand()).getValue()));
            } else {
                trunc.getFirstOperand().setRegister(trunc.getFirstOperand().getRegister());
            }

            if (trunc.getFirstOperand().getRegister() instanceof Immediate) {
                // 如果是立即数，直接对值进行截断处理
                int value = ((Immediate) trunc.getFirstOperand().getRegister()).getValue();
                int truncatedValue = value & 0x7F;
                trunc.setRegister(new Immediate(truncatedValue));
            } else {
                // 如果是寄存器，使用 MIPS 指令进行截断
                Register inputReg = (Register) trunc.getFirstOperand().getRegister();
                Register temp = Register.getTempRegister(currFunc);
                currBlock.addInstruction(new MipsBinaryOp("&", temp, inputReg, new Immediate(0x7F)));
                trunc.setRegister(temp);
            }
        }
    }

    public int buildAllocas(Alloca alloca) {
        Type allocType = alloca.getAllocType();
        return allocType.getSize();
    }

    public void initBuffer(String outputFile) throws IOException {
        writer = new BufferedWriter(new FileWriter(outputFile));
    }

    public void closeBuffer() throws IOException {
        writer.close();
    }

    public void showMips() throws IOException {
        buildMipsModule();
        writer.write(mipsModule.toString());
    }
}
